#!/usr/bin/python3

import datetime
import socket
import codecs
import os


## defining these functions to make code easier to read and edit

def readfile(path, suppress_filenotfound_error):
    try:
        return codecs.open(path, "r", encoding = "UTF-8").read()

    except FileNotFoundError:
        if not suppress_filenotfound_error:
            print("No such file: {}".format(path))

        return False

    except Exception as e:
        print("Failed to read file {} ({})".format(path, e))
        return False

def get_current_date():
    return datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S-%f")

def write_message(mail_from, rcpt_to, data, addr):
    ## also preventing it from overwriting existing files
    current_antidup_number = 0
    current_time = get_current_date()
    while True:
        try:
            if current_antidup_number == 0:
                open("messages/{}_{}.txt".format(current_time, addr), "r")
            else:
                open("messages/{}_{}-{}.txt".format(current_time, addr, current_antidup_number), "r")

        except FileNotFoundError:
            break

    if current_antidup_number == 0:
        filename = "messages/{}_{}.txt".format(current_time, addr)
    else:
        filename = "messages/{}_{}-{}.txt".format(current_time, addr, current_antidup_number)

    f = open(filename, "w", encoding = "UTF-8")
    f.write("FROM: {}\r\nTO: {}\r\nDATA:\r\n{}".format(mail_from, rcpt_to, data))
    f.close()

    return filename

def intify(x):
    try:
        return int(x)
    except Exception as e:
        print("Failed to parse integer from {} ({})".format(x, e))
        return False


## creating missing directories

required_dirs = ["config", "logs", "messages"]

for directory in required_dirs:
    try:
        os.listdir(directory)

    except FileNotFoundError:
        print("[NOTICE] Creating {}/ folder".format(directory))
        os.mkdir(directory)

    except Exception as e:
        print("[ERROR] Can't access {}/ directory, it might cause following errors (current error: {})".format(directory, e))


## starting the server

listening_port = readfile("config/port", True)

if listening_port == False:
    listening_port = 2525

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind( ("", intify(listening_port)) )
s.listen()

while True:
    try:
        conn, addr = s.accept()
        conn.settimeout(1800)

        print("[LOG] Accepted connection from {}:{}".format(addr[0], addr[1]))

        conn.send("220 {}\r\n".format( readfile("config/domain", True) ).encode("UTF-8"))

        if readfile("config/debug", True) != False:
            print("[DEBUG] responce given")

        connection_alive = True
        data_buffer = bytes()
        receiving_mail_body = False
        reset_buffer = False

        mail_from = ""
        rcpt_to = ""
        data = ""

        while connection_alive:
            while True:
                if reset_buffer:
                    data_buffer = bytes()
                    reset_buffer = False

                new_data = conn.recv(1024)

                if readfile("config/debug", True) != False:
                    print("[DEBUG] New_data: {}".format(new_data))

                if len(new_data) == 0:
                    print("[LOG] Connection {}:{} closed early".format(addr[0], addr[1]))
                    connection_alive = False
                    break

                data_buffer += new_data

                if not receiving_mail_body:
                    if data_buffer[-2:] == "\r\n".encode("UTF-8"):
                        reset_buffer = True
                        break
                else:
                    if data_buffer[-5:] == "\r\n.\r\n".encode("UTF-8"):
                        reset_buffer = True
                        data = data_buffer[:-5].decode("UTF-8")

                        fn = write_message(mail_from, rcpt_to, data, addr[0] + "_" + str(addr[1]))

                        conn.send("250 OK: saved as {}\r\n".format(fn).encode("UTF-8"))
                        receiving_mail_body = False
                        break

            command = data_buffer.decode("UTF-8")

            if not receiving_mail_body:
                if command[:4] == "HELO":
                    conn.send("250 {}\r\n".format( readfile("config/domain", True) ).encode("UTF-8"))

                elif command[:4] == "EHLO":
                    conn.send("250 {}\r\n".format( readfile("config/domain", True) ).encode("UTF-8"))

                elif command[:10] == "MAIL FROM:":
                    mail_from = command[10:-2]
                    conn.send("250 OK\r\n".encode("UTF-8"))

                elif command[:8] == "RCPT TO:":
                    rcpt_to = command[8:-2]
                    conn.send("250 OK\r\n".encode("UTF-8"))

                elif command[:4] == "DATA":
                    receiving_mail_body = True
                    conn.send("354\r\n".encode("UTF-8"))

                elif command[:4] == "QUIT":
                    conn.send("221 Bye\r\n".encode("UTF-8"))
                    conn.close()
                    connection_alive = False
                    print("[LOG] Connection {}:{} closed".format(addr[0], addr[1]))
                    continue


    except KeyboardInterrupt:
        print("\rStopping the server...")
        break

    except Exception as e:
        print("[ERROR] Unexpected error on connection {}:{} ({})".format(addr[0], addr[1], e))
        conn.close()
